/*
#
#  Copyright 2011 Matthew Jannace
#
#  This file is part of RoboCode BlackHoleAgent
#
#  RoboCode BlackHoleAgent is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  RoboCode BlackHoleAgent is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with RoboCode BlackHoleAgent. If not, see
#  <http://www.gnu.org/licenses/>.
#
 */
package com.jannaceltd.TeamBlackHoleBeer.Modules.Targeting;

import com.jannaceltd.TeamBlackHoleBeer.Modules.Targeting.TargetingMethods.GuessFactorTargeting;
import com.jannaceltd.TeamBlackHoleBeer.Modules.Targeting.TargetingMethods.HeadOnTargeting;
import com.jannaceltd.TeamBlackHoleBeer.Modules.Targeting.TargetingMethods.ExactLinearTargeting;
import com.jannaceltd.TeamBlackHoleBeer.BlackHole_Abstract;
import com.jannaceltd.TeamBlackHoleBeer.BlackHole_Interface;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.TeamMate;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.VirtualBullets;
import com.jannaceltd.TeamBlackHoleBeer.Utilities.MathUtils;
import com.jannaceltd.TeamBlackHoleBeer.Utilities.PolygonUtilities;
import java.awt.Polygon;
import java.awt.geom.Point2D;
import robocode.Bullet;
import robocode.util.Utils;

/**
 * Class that controls all the Robot Targeting Systems
 * @author Matthew Jannace <mjannace@gmail.com>
 * @version 1.0
 */
public class Targeting implements BlackHole_Interface {

    private BlackHole_Abstract Agent;
    /**
     * Object that Controls Guess FactorTargeting
     */
    public GuessFactorTargeting GFTargeting = null;
    /**
     * Object that Controls Exact Linear Targeting
     */
    public ExactLinearTargeting ELinTargeting = null;
    /**
     * Object that Controls Head On Targeting
     */
    public HeadOnTargeting HOnTargeting = null;
    private int fireCounter = 0;
    private int hitCounter = 0;
    private int enemyDamage = 0;
    private Point2D Target_InterSect_P1 = null;
    private Point2D Target_InterSect_P2 = null;
    private Point2D Agent_InterSect_P1 = null;
    private Point2D Agent_InterSect_P2 = null;
    private boolean targetInRange;
    private double fieldSize;
    private double disToTarget;
    /**
     * The Current Targeting System
     */
    protected TargetingSystem currentTargetingSystem;

    /**
     * Creates a Targeting Object
     * @param TeamAgent
     */
    public Targeting(BlackHole_Abstract TeamAgent) {
        this.Agent = TeamAgent;
        currentTargetingSystem = TargetingSystem.exactLinear;
        GFTargeting = new GuessFactorTargeting(Agent, this);
        ELinTargeting = new ExactLinearTargeting(Agent, this);
        HOnTargeting = new HeadOnTargeting(Agent, this);
        targetInRange = true;
        fieldSize = Agent.getBattleFieldWidth() + Agent.getBattleFieldHeight();
    }

    /**
     * Performs a Shot
     */
    public void doShoot() {

        switch (currentTargetingSystem) {
            case headOn:
                HOnTargeting.doHeadOnTargeting();
                break;
            case exactLinear:
                ELinTargeting.doExactLinearTargeting();
                break;
            case guessFactor:
                GFTargeting.doGuessFactorTargeting();
                break;
            default:
                System.out.println("No Targeting System Set");
        }
    }

    /**
     * Take A shot if there are no teammates in the path and the power of the
     * bullet is greater than 0.1.
     * @param firepower
     */
    public void dofire(double firepower) {

        disToTarget = Agent.getPos().distance(Agent.target.getPos());

        if (disToTarget <= (fieldSize) / 3 && Agent.teamates.size() < 1 && Agent.getOthers() < 1) {
            targetInRange = false;
        } else {
            targetInRange = true;
        }

        if (SafeToShoot() && targetInRange) {

            firepower = MathUtils.Limit(firepower, 0, 3);
            if (firepower >= 0.1 && Agent.getGunHeat() == 0) {
                Bullet bullet = null;
                bullet = Agent.setFireBullet(firepower);
                if (bullet != null) {
                    VirtualBullets vb = new VirtualBullets(
                            /* Name */((bullet.getName() + fireCounter)),
                            /* fireSourceLocation */ (new Point2D.Double(bullet.getX(), bullet.getY())),
                            /* fireTime */ (Agent.getTime()),
                            /* firePower */ (bullet.getPower()),
                            /* Velocity */ (bullet.getVelocity()),
                            /* HeadingRadians */ (bullet.getHeadingRadians()));

                    Agent.myFire.put(vb.getName(), vb);
                    fireCounter++;
                    Agent.comm.doBroadcastBullet(vb, FriendlyFireState.ACTIVE);
                }
            }
        }
    }

    /**
     * Determine if there are teammates are near the possible path of a fired
     * bullet.
     *
     * @return True if There are no Teammates in the Danger Area else False There are Teammates in the Danger Area
     */
    private boolean SafeToShoot() {

        double FireAngle = 0;
        Point2D.Double myPos = Agent.getPos();
        Point2D.Double targetPos = Agent.target.getPos();
        //Tagets Abs Angle
        //double TargAbsAngle = Utils.normalAbsoluteAngle(Math.atan2(targetPos.getX() - myPos.getX(), targetPos.getY() - myPos.getY()));
        //Gun Barrel Abs Angle
        double TargAbsAngle = Utils.normalAbsoluteAngle(Agent.getGunHeadingRadians());

        double distanceToTarget = myPos.distance(targetPos);

        FireAngle = 0.25;

        Point2D target_P1, target_P2, botToTarg_P1, botToTarg_P2;

        target_P1 = MathUtils.project(targetPos, (TargAbsAngle + (Math.PI / 2)), (100));
        target_P2 = MathUtils.project(targetPos, (TargAbsAngle - (Math.PI / 2)), (100));


        int IntialFireWidth = 60;
        // 90 line parralle to target extending past the Agents current position
        Agent_InterSect_P1 = MathUtils.project(myPos, (TargAbsAngle + (Math.PI / 2)), (IntialFireWidth / 2));
        Agent_InterSect_P2 = MathUtils.project(myPos, (TargAbsAngle - (Math.PI / 2)), (IntialFireWidth / 2));


        // Line 1 from Robot to Targets
        botToTarg_P1 = MathUtils.project((Point2D.Double) Agent_InterSect_P1, (TargAbsAngle + FireAngle), (distanceToTarget / 2));
        botToTarg_P2 = MathUtils.project((Point2D.Double) Agent_InterSect_P1, (TargAbsAngle + FireAngle), distanceToTarget);

        // Itersection between the line 1 from robot to Targets 90 degree line
        Target_InterSect_P1 = PolygonUtilities.intersection(botToTarg_P1, botToTarg_P2, target_P1, target_P2);

        // Line 2 from Robot to Targets
        botToTarg_P1 = MathUtils.project((Point2D.Double) Agent_InterSect_P2, (TargAbsAngle - FireAngle), (distanceToTarget / 2));
        botToTarg_P2 = MathUtils.project((Point2D.Double) Agent_InterSect_P2, (TargAbsAngle - FireAngle), distanceToTarget);

        // Itersection between the line 2 from robot to Targets 90 degree line
        Target_InterSect_P2 = PolygonUtilities.intersection(botToTarg_P1, botToTarg_P2, target_P1, target_P2);

        Polygon poly = new Polygon();

        poly.addPoint((int) Agent_InterSect_P1.getX(), (int) Agent_InterSect_P1.getY());
        poly.addPoint((int) Agent_InterSect_P2.getX(), (int) Agent_InterSect_P2.getY());

        poly.addPoint((int) Target_InterSect_P1.getX(), (int) Target_InterSect_P1.getY());
        poly.addPoint((int) Target_InterSect_P2.getX(), (int) Target_InterSect_P2.getY());

        // Test if A teammate is in the Fire Range Triangle

        for (TeamMate tm : Agent.teamates.values()) {
            if (tm.getAlive()) {
                // Test Center
                if (poly.contains(tm.getPos().getX(), tm.getPos().getY())
                        // Test Upper Left
                        || poly.contains((tm.getPos().getX() - 20), tm.getPos().getY() + 20)
                        // Test Lower Left
                        || poly.contains((tm.getPos().getX() - 20), tm.getPos().getY() - 20)
                        // Test Upper Right
                        || poly.contains((tm.getPos().getX() + 20), tm.getPos().getY() + 20)
                        // Test Lower Right
                        || poly.contains((tm.getPos().getX() + 20), tm.getPos().getY() - 20)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Remove Tracked bullets that the agent fired, notify teammates to remove
     * the bullet also.
     *
     * @param bullet
     * @param HitTarget
     */
    public void doRemoveMyFire(Bullet bullet, boolean HitTarget) {
        if (HitTarget && !Agent.isTeammate(bullet.getVictim())) {
            hitCounter++;
            enemyDamage += MathUtils.bulletDamage(bullet.getPower());
        }
        for (VirtualBullets vb : Agent.myFire.values()) {
            if (vb.getHeadingRadians() == bullet.getHeadingRadians()) {
                Agent.comm.doBroadcastBullet(new VirtualBullets(Agent.myFire.get(vb.getName())), FriendlyFireState.INACTIVE);
                Agent.myFire.remove(vb.getName());
                return;
            }
        }
    }

    /**
     * Gets An intersection of the line from the Agent line to the Target Line
     * @return Agent_InterSect_P1
     */
    public Point2D getAgent_InterSect_P1() {
        return Agent_InterSect_P1;
    }

    /**
     * Gets An intersection of the line from the Agent line to the Target Line
     * @return Agent_InterSect_P2
     */
    public Point2D getAgent_InterSect_P2() {
        return Agent_InterSect_P2;
    }

    /**
     * Gets An intersection of the line from the Target line to the Agent Line
     * @return Target_InterSect_P1
     */
    public Point2D getTarget_InterSect_P1() {
        return Target_InterSect_P1;
    }

    /**
     * Gets An intersection of the line from the Target line to the Agent Line
     * @return Target_InterSect_P2
     */
    public Point2D getTarget_InterSect_P2() {
        return Target_InterSect_P2;
    }

    /**
     * The Number of Bullets Fired
     * @return fireCounter
     */
    public int getFireCounter() {
        return fireCounter;
    }

    /**
     * The Number of Bullets that hit a target
     * @return hitCounter
     */
    public int getHitCounter() {
        return hitCounter;
    }

    /**
     * The amount of damage An agent has done
     * @return enemyDamage
     */
    public int getEnemyDamage() {
        return enemyDamage;
    }
}
